package com.freewater.cargotracker.domain.model.cargo;

import com.freewater.cargotracker.domain.common.Aggregate;
import com.freewater.cargotracker.domain.model.location.Location;
import com.github.meixuesong.aggregatepersistence.Versionable;
import org.apache.commons.lang3.Validate;

import java.io.Serializable;
import java.util.Objects;

public class Cargo implements Serializable, Versionable, Aggregate {

    private static final long serialVersionUID = 1L;

    private Long id;
    private TrackingId trackingId;
    private Location origin;
    private RouteSpecification routeSpecification;
    private CargoCustomers cargoCustomers;
    private Itinerary itinerary;
    private Double size;
    private int version; // 乐观锁版本号，默认为0。注意：这个版本号为0时，代表是新增的实体

    public Cargo(TrackingId trackingId, RouteSpecification routeSpecification) {
        Validate.notNull(trackingId, "Tracking ID is required");
        Validate.notNull(routeSpecification, "Route specification is required");

        this.trackingId = trackingId;
        // Cargo origin never changes, even if the route specification changes.
        // However, at creation, cargo origin can be derived from the initial
        // route specification.
        this.origin = routeSpecification.getOrigin();
        this.routeSpecification = routeSpecification;
        this.cargoCustomers = CargoCustomers.EMPTY_CUSTOMERS;
        this.itinerary = Itinerary.EMPTY_ITINERARY;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        Validate.notNull(id, "id is required");
        this.id = id;
    }

    public TrackingId getTrackingId() {
        return trackingId;
    }

    public Location getOrigin() {
        return origin;
    }

    public void setOrigin(Location origin) {
        this.origin = origin;
    }

    public RouteSpecification getRouteSpecification() {
        return routeSpecification;
    }

    public CargoCustomers getCargoCustomers() {
        return cargoCustomers;
    }

    public void setCargoCustomers(CargoCustomers cargoCustomers) {
        Validate.notNull(cargoCustomers, "Customers is required");
        this.cargoCustomers = cargoCustomers;
    }

    public Itinerary getItinerary() {
        return itinerary;
    }

    public Double getSize() {
        return size;
    }

    public void setSize(Double size) {
        Validate.notNull(size, "size can not be null");
        Validate.isTrue(size > 0, "size should be greater than zero");
        this.size = size;
    }

    @Override
    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    /**
     * 修改Route Specification
     *
     * @param routeSpecification
     */
    public void specifyNewRouteSpec(RouteSpecification routeSpecification) {
        Validate.notNull(routeSpecification, "Route specification is required");
        this.routeSpecification = routeSpecification;
    }

    /**
     * 绑定到航信日程
     *
     * @param itinerary
     */
    public void assignToRoute(Itinerary itinerary) {
        Validate.notNull(itinerary, "Itinerary is required");
        this.itinerary = itinerary;
    }

    /**
     * 已当前货物为原型生成新的cargo：
     * 1、指定新的trackingID；
     * 2、空的DeliveryHistory
     * 3、其他的与当前cargo相同
     *
     * @param trackingId
     * @return
     */
    public Cargo copyPrototype(TrackingId trackingId) {
        Cargo cargo = new Cargo(trackingId, this.routeSpecification);
        cargo.cargoCustomers = new CargoCustomers(cargo.getCargoCustomers());
        return cargo;
    }

    /**
     * @param obj to compare
     * @return True if they have the same identity
     * @see #sameValueAs(Cargo)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }
        final Cargo other = (Cargo) obj;
        return this.sameValueAs(other);
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.trackingId);
    }

    @Override
    public String toString() {
        return "Cargo{" +
                "id=" + id +
                ", trackingID=" + trackingId +
                '}';
    }

    private boolean sameValueAs(Cargo other) {
        return other != null
                && this.trackingId.sameValueAs(other.trackingId);
    }
}
